import { CssBaseline } from "@mui/material";
import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import React, { Suspense, useEffect, useState } from "react";
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
import {
  authenticateWithToken,
  getAuthenticationMode,
} from "./authentication/authentication";
import { AUTHENTICATION_ERROR_EVENT } from "./authentication/constants";
import TokenAuthenticationDialog from "./authentication/TokenAuthenticationDialog";
import ActorDetailPage, { ActorDetailLayout } from "./pages/actor/ActorDetail";
import { ActorLayout } from "./pages/actor/ActorLayout";
import Loading from "./pages/exception/Loading";
import JobList, { JobsLayout } from "./pages/job";
import { JobDetailChartsPage } from "./pages/job/JobDetail";
import {
  JobDetailActorDetailWrapper,
  JobDetailActorsPage,
} from "./pages/job/JobDetailActorPage";
import { JobDetailInfoPage } from "./pages/job/JobDetailInfoPage";
import { JobDetailLayout, JobPage } from "./pages/job/JobDetailLayout";
import { MainNavLayout } from "./pages/layout/MainNavLayout";
import { SideTabPage } from "./pages/layout/SideTabLayout";
import {
  LogsLayout,
  StateApiLogsListPage,
  StateApiLogViewerPage,
} from "./pages/log/Logs";
import { Metrics } from "./pages/metrics";
import {
  DashboardUids,
  getMetricsInfo,
  getTimeZoneInfo,
  TimezoneInfo,
} from "./pages/metrics/utils";
import Nodes, { ClusterMainPageLayout } from "./pages/node";
import { ClusterDetailInfoPage } from "./pages/node/ClusterDetailInfoPage";
import { ClusterLayout } from "./pages/node/ClusterLayout";
import NodeDetailPage from "./pages/node/NodeDetail";
import { OverviewPage } from "./pages/overview/OverviewPage";
import {
  ServeApplicationDetailLayout,
  ServeApplicationDetailPage,
} from "./pages/serve/ServeApplicationDetailPage";
import {
  ServeDeploymentDetailLayout,
  ServeDeploymentDetailPage,
} from "./pages/serve/ServeDeploymentDetailPage";
import { ServeDeploymentsListPage } from "./pages/serve/ServeDeploymentsListPage";
import { ServeLayout, ServeSideTabLayout } from "./pages/serve/ServeLayout";
import { ServeReplicaDetailLayout } from "./pages/serve/ServeReplicaDetailLayout";
import { ServeReplicaDetailPage } from "./pages/serve/ServeReplicaDetailPage";
import {
  ServeControllerDetailPage,
  ServeProxyDetailPage,
} from "./pages/serve/ServeSystemActorDetailPage";
import {
  ServeSystemDetailLayout,
  ServeSystemDetailPage,
} from "./pages/serve/ServeSystemDetailPage";
import { TaskPage } from "./pages/task/TaskPage";
import { getNodeList } from "./service/node";
import { darkTheme, lightTheme } from "./theme";

dayjs.extend(duration);

// lazy loading fro prevent loading too much code at once
const Actors = React.lazy(() => import("./pages/actor"));
const CMDResult = React.lazy(() => import("./pages/cmd/CMDResult"));

// a global map for relations
export type GlobalContextType = {
  nodeMap: { [key: string]: string };
  nodeMapByIp: { [key: string]: string };
  namespaceMap: { [key: string]: string[] };
  /**
   * Whether the initial metrics context has been fetched or not.
   * This can be used to determine the difference between Grafana
   * not being set up vs the status not being fetched yet.
   */
  metricsContextLoaded: boolean;
  /**
   * The host that is serving grafana. Only set if grafana is
   * running as detected by the grafana healthcheck endpoint.
   */
  grafanaHost: string | undefined;
  /**
   * The param 'orgId' used in grafana. Default is 1.
   */
  grafanaOrgId: string;
  /**
   * The filter for the Cluster variable in grafana dashboards.
   */
  grafanaClusterFilter: string | undefined;
  /**
   * The uids of the dashboards that ray exports that powers the various metrics UIs.
   */
  dashboardUids: DashboardUids | undefined;
  /**
   * Whether prometheus is runing or not
   */
  prometheusHealth: boolean | undefined;
  /**
   * The name of the currently running ray session.
   */
  sessionName: string | undefined;
  /**
   * The name of the current selected datasource.
   */
  dashboardDatasource: string | undefined;
  /**
   * The timezone set on the ray cluster.
   */
  serverTimeZone: TimezoneInfo | null | undefined;
  /**
   * The globally selected current time zone.
   */
  currentTimeZone: string | undefined;
  /**
   * The current theme mode (light or dark)
   */
  themeMode: "light" | "dark";
  /**
   * Function to toggle between light and dark mode
   */
  toggleTheme: () => void;
};
export const GlobalContext = React.createContext<GlobalContextType>({
  nodeMap: {},
  nodeMapByIp: {},
  namespaceMap: {},
  metricsContextLoaded: false,
  grafanaHost: undefined,
  grafanaOrgId: "1",
  grafanaClusterFilter: undefined,
  dashboardUids: undefined,
  prometheusHealth: undefined,
  sessionName: undefined,
  dashboardDatasource: undefined,
  serverTimeZone: undefined,
  currentTimeZone: undefined,
  themeMode: "light",
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  toggleTheme: () => {},
});

const App = () => {
  const [currentTimeZone, setCurrentTimeZone] = useState<string>();
  const [themeMode, setThemeMode] = useState<"light" | "dark">(() => {
    // Check URL query param first (for embedded views)
    const urlParams = new URLSearchParams(window.location.search);
    const urlTheme = urlParams.get("theme");
    if (urlTheme === "dark" || urlTheme === "light") {
      return urlTheme;
    }
    // Then check localStorage
    const stored = localStorage.getItem("themeMode");
    if (stored === "dark" || stored === "light") {
      return stored;
    }
    // Fall back to system preference
    if (
      window.matchMedia &&
      window.matchMedia("(prefers-color-scheme: dark)").matches
    ) {
      return "dark";
    }
    return "light";
  });
  const [context, setContext] = useState<
    Omit<GlobalContextType, "currentTimeZone" | "themeMode" | "toggleTheme">
  >({
    nodeMap: {},
    nodeMapByIp: {},
    namespaceMap: {},
    metricsContextLoaded: false,
    grafanaHost: undefined,
    grafanaOrgId: "1",
    grafanaClusterFilter: undefined,
    dashboardUids: undefined,
    prometheusHealth: undefined,
    sessionName: undefined,
    dashboardDatasource: undefined,
    serverTimeZone: undefined,
  });

  const toggleTheme = () => {
    setThemeMode((prevMode) => {
      const newMode = prevMode === "light" ? "dark" : "light";
      localStorage.setItem("themeMode", newMode);
      return newMode;
    });
  };

  // Listen for system theme changes
  useEffect(() => {
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleChange = (e: MediaQueryListEvent) => {
      // Only update if user hasn't explicitly set a preference via localStorage or URL
      const stored = localStorage.getItem("themeMode");
      const urlParams = new URLSearchParams(window.location.search);
      const urlTheme = urlParams.get("theme");
      if (!stored && !urlTheme) {
        setThemeMode(e.matches ? "dark" : "light");
      }
    };

    mediaQuery.addEventListener("change", handleChange);
    return () => mediaQuery.removeEventListener("change", handleChange);
  }, []);

  // Authentication state
  const [authenticationDialogOpen, setAuthenticationDialogOpen] =
    useState(false);
  const [hasAttemptedAuthentication, setHasAttemptedAuthentication] =
    useState(false);
  const [authenticationError, setAuthenticationError] =
    useState<string | undefined>();
  useEffect(() => {
    getNodeList().then((res) => {
      if (res?.data?.data?.summary) {
        const nodeMap = {} as { [key: string]: string };
        const nodeMapByIp = {} as { [key: string]: string };
        res.data.data.summary.forEach(({ hostname, raylet, ip }) => {
          nodeMap[hostname] = raylet.nodeId;
          nodeMapByIp[ip] = raylet.nodeId;
        });
        setContext((existingContext) => ({
          ...existingContext,
          nodeMap,
          nodeMapByIp,
          namespaceMap: {},
        }));
      }
    });
  }, []);

  // Detect if grafana is running
  useEffect(() => {
    const doEffect = async () => {
      const {
        grafanaHost,
        grafanaOrgId,
        grafanaClusterFilter,
        sessionName,
        prometheusHealth,
        dashboardUids,
        dashboardDatasource,
      } = await getMetricsInfo();
      setContext((existingContext) => ({
        ...existingContext,
        metricsContextLoaded: true,
        grafanaHost,
        grafanaOrgId,
        grafanaClusterFilter,
        dashboardUids,
        sessionName,
        prometheusHealth,
        dashboardDatasource,
      }));
    };
    doEffect();
  }, []);

  useEffect(() => {
    const updateTimezone = async () => {
      // Sets the initial timezone to localStorage value if it exists
      const storedTimeZone = localStorage.getItem("timezone");
      if (storedTimeZone) {
        setCurrentTimeZone(storedTimeZone);
      }

      // Fetch the server time zone.
      const tzInfo = await getTimeZoneInfo();

      const timeZone =
        storedTimeZone ||
        tzInfo?.value ||
        Intl.DateTimeFormat().resolvedOptions().timeZone;

      setCurrentTimeZone(timeZone);
      setContext((existingContext) => ({
        ...existingContext,
        serverTimeZone: tzInfo,
      }));
    };
    updateTimezone();
  }, []);

  // Check authentication mode on mount and cache in sessionStorage
  useEffect(() => {
    const checkAuthentication = async () => {
      try {
        const { authentication_mode } = await getAuthenticationMode();
        sessionStorage.setItem("ray-authentication-mode", authentication_mode);
      } catch (error) {
        console.error("Failed to check authentication mode:", error);
      }
    };

    checkAuthentication();
  }, []);

  // Listen for authentication errors from axios interceptor
  useEffect(() => {
    const handleAuthenticationError = async (event: Event) => {
      const customEvent = event as CustomEvent<{ hadToken: boolean }>;
      const hadToken = customEvent.detail?.hadToken ?? false;

      // Check cached auth mode first, fall back to API if not cached
      let authMode = sessionStorage.getItem("ray-authentication-mode");
      if (!authMode) {
        try {
          const { authentication_mode } = await getAuthenticationMode();
          authMode = authentication_mode;
          sessionStorage.setItem(
            "ray-authentication-mode",
            authentication_mode,
          );
        } catch (error) {
          console.error("Failed to check authentication mode:", error);
          return;
        }
      }

      if (authMode === "token" || authMode === "k8s") {
        setHasAttemptedAuthentication(hadToken);
        setAuthenticationDialogOpen(true);
      }
    };

    window.addEventListener(
      AUTHENTICATION_ERROR_EVENT,
      handleAuthenticationError,
    );

    return () => {
      window.removeEventListener(
        AUTHENTICATION_ERROR_EVENT,
        handleAuthenticationError,
      );
    };
  }, []);

  // Handle token submission from dialog
  const handleTokenSubmit = async (token: string) => {
    try {
      // Authenticate with the server - this will validate the token
      // and set an HttpOnly cookie if valid
      const isValid = await authenticateWithToken(token);

      if (isValid) {
        // Token is valid and server has set HttpOnly cookie
        setHasAttemptedAuthentication(true);
        setAuthenticationDialogOpen(false);
        setAuthenticationError(undefined);

        // Reload the page to refetch all data with the new token
        window.location.reload();
      } else {
        // Token is invalid
        setHasAttemptedAuthentication(true);
        setAuthenticationError(
          "Invalid authentication token. Please check and try again.",
        );
      }
    } catch (error) {
      console.error("Failed to authenticate:", error);
      setAuthenticationError(
        "Failed to authenticate. Please check your connection and try again.",
      );
    }
  };

  const currentTheme = themeMode === "dark" ? darkTheme : lightTheme;

  return (
    <StyledEngineProvider injectFirst>
      <ThemeProvider theme={currentTheme}>
        <Suspense fallback={Loading}>
          <GlobalContext.Provider
            value={{ ...context, currentTimeZone, themeMode, toggleTheme }}
          >
            <CssBaseline />
            <TokenAuthenticationDialog
              open={authenticationDialogOpen}
              hasExistingToken={hasAttemptedAuthentication}
              onSubmit={handleTokenSubmit}
              error={authenticationError}
            />
            <HashRouter>
              <Routes>
                {/* Redirect people hitting the /new path to root. TODO(aguo): Delete this redirect in ray 2.5 */}
                <Route element={<Navigate replace to="/" />} path="/new" />
                <Route element={<MainNavLayout />} path="/">
                  <Route element={<Navigate replace to="overview" />} path="" />
                  <Route element={<OverviewPage />} path="overview" />
                  <Route element={<ClusterMainPageLayout />} path="cluster">
                    <Route element={<ClusterLayout />} path="">
                      <Route
                        element={
                          <SideTabPage tabId="info">
                            <ClusterDetailInfoPage />
                          </SideTabPage>
                        }
                        path="info"
                      />
                      <Route
                        element={
                          <SideTabPage tabId="table">
                            <Nodes />
                          </SideTabPage>
                        }
                        path=""
                      />
                    </Route>
                    <Route element={<NodeDetailPage />} path="nodes/:id" />
                  </Route>
                  <Route element={<JobsLayout />} path="jobs">
                    <Route element={<JobList />} path="" />
                    <Route element={<JobPage />} path=":id">
                      <Route element={<JobDetailLayout />} path="">
                        <Route
                          element={
                            <SideTabPage tabId="info">
                              <JobDetailInfoPage />
                            </SideTabPage>
                          }
                          path="info"
                        />
                        <Route
                          element={
                            <SideTabPage tabId="charts">
                              <JobDetailChartsPage />
                            </SideTabPage>
                          }
                          path=""
                        />
                        <Route
                          element={
                            <SideTabPage tabId="actors">
                              <JobDetailActorsPage />
                            </SideTabPage>
                          }
                          path="actors"
                        />
                      </Route>
                      <Route
                        element={
                          <JobDetailActorDetailWrapper>
                            <ActorDetailLayout />
                          </JobDetailActorDetailWrapper>
                        }
                        path="actors/:actorId"
                      >
                        <Route element={<ActorDetailPage />} path="" />
                        <Route element={<TaskPage />} path="tasks/:taskId" />
                      </Route>
                      <Route element={<TaskPage />} path="tasks/:taskId" />
                    </Route>
                  </Route>
                  <Route element={<ActorLayout />} path="actors">
                    <Route element={<Actors />} path="" />
                    <Route element={<ActorDetailLayout />} path=":actorId">
                      <Route element={<ActorDetailPage />} path="" />
                      <Route element={<TaskPage />} path="tasks/:taskId" />
                    </Route>
                  </Route>
                  <Route element={<Metrics />} path="metrics" />
                  <Route element={<ServeLayout />} path="serve">
                    <Route element={<ServeSideTabLayout />} path="">
                      <Route
                        element={
                          <SideTabPage tabId="system">
                            <ServeSystemDetailPage />
                          </SideTabPage>
                        }
                        path="system"
                      />
                      <Route
                        element={
                          <SideTabPage tabId="deployments">
                            <ServeDeploymentsListPage />
                          </SideTabPage>
                        }
                        path=""
                      />
                    </Route>
                    <Route element={<ServeSystemDetailLayout />} path="system">
                      <Route
                        element={<ServeControllerDetailPage />}
                        path="controller"
                      />
                      <Route
                        element={<ServeProxyDetailPage />}
                        path="proxies/:proxyId"
                      />
                    </Route>
                    <Route
                      element={<ServeApplicationDetailLayout />}
                      path="applications/:applicationName"
                    >
                      <Route element={<ServeApplicationDetailPage />} path="" />
                      <Route
                        element={<ServeDeploymentDetailLayout />}
                        path=":deploymentName"
                      >
                        <Route
                          element={<ServeDeploymentDetailPage />}
                          path=""
                        />
                        <Route
                          element={<ServeReplicaDetailLayout />}
                          path=":replicaId"
                        >
                          <Route element={<ServeReplicaDetailPage />} path="" />
                          <Route path="tasks/:taskId" element={<TaskPage />} />
                        </Route>
                      </Route>
                    </Route>
                  </Route>
                  <Route element={<LogsLayout />} path="logs">
                    <Route element={<StateApiLogsListPage />} path="" />
                    <Route element={<StateApiLogViewerPage />} path="viewer" />
                  </Route>
                </Route>
                <Route element={<CMDResult />} path="/cmd/:cmd/:ip/:pid" />
              </Routes>
            </HashRouter>
          </GlobalContext.Provider>
        </Suspense>
      </ThemeProvider>
    </StyledEngineProvider>
  );
};

export default App;
